﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Innovation;
using System.IO;
using Microsoft.Xna.Framework.Storage;
using System.Xml.Serialization;
using Innovation.Serialization;
using System.Xml;
using System;

namespace Innovation
{
    public static class Engine
    {
        // The GraphicsDevice the engine is using
        public static GraphicsDevice GraphicsDevice;

        // The Engine's SpriteBatch
        public static SpriteBatch SpriteBatch;

        // The collection of GameScreens we are managinng
        public static GameScreenCollection GameScreens = new GameScreenCollection();

        // The current GameTime
        public static GameTime GameTime;

        // The engine's service container
        public static IEServiceContainer Services;

        // The engine's content manager
        public static IEContentManager Content;

        // GameScreen provided by the engine.
        public static GameScreen BackgroundScreen;

        // The GameScreen to set to new GameScreens when a screen is not specified
        public static GameScreen DefaultScreen;

        // Whether the engine has been initialized with SetupEngine()
        public static bool IsInitialized = false;

        // Initializes the engine
        public static void SetupEngine(IGraphicsDeviceService GraphicsDeviceService)
        {
            // Setup the GraphicsDevice and SpriteBatch
            Engine.GraphicsDevice = GraphicsDeviceService.GraphicsDevice;
            Engine.SpriteBatch = new SpriteBatch(GraphicsDeviceService.GraphicsDevice);

            // Setup the service container and add the IGraphicsDeviceService to it
            Engine.Services = new IEServiceContainer();
            Engine.Services.AddService(typeof(IGraphicsDeviceService),
                GraphicsDeviceService);

            // Setup the content manager using the service container
            Content = new IEContentManager(Services);

            // Setup the background screen
            BackgroundScreen = new GameScreen("Engine.BackgroundScreen");
            BackgroundScreen.OverrideUpdateBlocked = true;
            BackgroundScreen.OverrideDrawBlocked = true;
            BackgroundScreen.OverrideInputBlocked = true;

            // Set the default screen to the background screen so new screens will
            // use it automatically unless told otherwise
            DefaultScreen = BackgroundScreen;

            // Let components know the engine is ready to accept them
            IsInitialized = true;
        }

        // Update the engine, screens, and components
        public static void Update(GameTime gameTime)
        {
            // Update the game time
            Engine.GameTime = gameTime;

            // Create a temporary list
            List<GameScreen> updating = new List<GameScreen>();

            // Populate the temp list
            foreach (GameScreen screen in GameScreens)
                updating.Add(screen);

            // BlocksUpdate and OverrideUpdateBlocked login
            for (int i = GameScreens.Count - 1; i >= 0; i--)
                if (GameScreens[i].BlocksUpdate)
                {
                    if (i > 0)
                        for (int j = i - 1; j >= 0; j--)
                            if (!GameScreens[j].OverrideUpdateBlocked)
                                updating.Remove(GameScreens[j]);

                    break;
                }

            // Update remaining components
            foreach (GameScreen screen in updating)
                if (screen.Initialized)
                    screen.Update();

            // Clear list
            updating.Clear();

            // Repopulate list
            foreach (GameScreen screen in GameScreens)
                updating.Add(screen);

            // BlocksInput and OverrideInputBlocked login
            for (int i = GameScreens.Count - 1; i >= 0; i--)
                if (GameScreens[i].BlocksInput)
                {
                    if (i > 0)
                        for (int j = i - 1; j >= 0; j--)
                            if (!GameScreens[j].OverrideInputBlocked)
                                updating.Remove(GameScreens[j]);

                    break;
                }

            // Set IsInputAllowed for all GameScreens
            foreach (GameScreen screen in GameScreens)
                if (!screen.InputDisabled)
                    screen.IsInputAllowed = updating.Contains(screen);
                else
                    screen.IsInputAllowed = false;
        }

        // Draws the current collection of screens and components. Accepts a 
        // ComponentType to render
        public static void Draw(GameTime gameTime, ComponentPredicate RenderType, bool PreDraw)
        {
            // Update the time, create a temp list
            Engine.GameTime = gameTime;
            List<GameScreen> drawing = new List<GameScreen>();

            // Clear the back buffer
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // Populate the temp list if the screen is visible
            foreach (GameScreen screen in GameScreens)
                if (screen.Visible)
                    drawing.Add(screen);

            // BlocksDraw and OverrideDrawBlocked logic
            for (int i = GameScreens.Count - 1; i >= 0; i--)
                if (GameScreens[i].BlocksDraw)
                {
                    if (i > 0)
                        for (int j = i - 1; j >= 0; j--)
                        {
                            if (!GameScreens[j].OverrideDrawBlocked)
                                drawing.Remove(GameScreens[j]);
                        }

                    break;
                }

            // Draw the remaining screens
            foreach (GameScreen screen in drawing)
                if (screen.Initialized)
                    screen.Draw(RenderType, PreDraw);
        }

        // Draw function that automatically creates the predicate based on type
        public static void Draw(GameTime gameTime, ComponentType RenderType)
        {
            Draw(gameTime, new ComponentTypePredicate(RenderType), true);
        }

        // Draw function that automatically creates the predicate based on type
        public static void Draw(GameTime gameTime, ComponentType RenderType, bool PreDraw)
        {
            Draw(gameTime, new ComponentTypePredicate(RenderType), PreDraw);
        }

        // Resets the Engine to its initial state
        public static void Reset()
        {
            List<Component> destroy = new List<Component>();

            foreach (GameScreen screen in Engine.GameScreens)
                foreach (Component component in screen.Components)
                    destroy.Add(component);

            foreach (Component component in destroy)
                component.DisableComponent();

            List<GameScreen> screenDestroy = new List<GameScreen>();

            foreach (GameScreen screen in Engine.GameScreens)
                if (screen != Engine.BackgroundScreen)
                    screenDestroy.Add(screen);

            foreach (GameScreen screen in screenDestroy)
                screen.Disable();

            Engine.Services.Clear();
            Engine.Content.Unload();
        }

        // Save the current state of the engine to file
        public static void SerializeState(string Filename)
        {
            // Get the start time
            DateTime startTime = DateTime.Now;

            // Create an XmlWriter
            XmlWriterSettings set = new XmlWriterSettings();
            set.Indent = true;
            XmlWriter writer = XmlWriter.Create(new FileStream(Filename, FileMode.Create), set);

            // Create a Serializer
            Serializer s = new Serializer();

            // Write the start of the document, including the root node and save time
            writer.WriteStartDocument();
            writer.WriteStartElement("EngineState");
            writer.WriteAttributeString("Time", startTime.ToString());

            // Serialize the list of GameScreens
            s.WriteGameScreens(writer);

            // Write the component root node
            writer.WriteStartElement("Components");

            // Serialize all the components, if they want to be serialized
            foreach(GameScreen gameScreen in GameScreens)
                foreach (Component component in gameScreen.Components)
                    if (component.Serialize)
                    {
                        writer.WriteStartElement(component.GetType().FullName);
                        s.Serialize(writer, component.GetSerializationData(s));
                        writer.WriteEndElement();
                    }

            // Finish the Components node
            writer.WriteEndElement();

            // Write out Assembly dependencies
            s.WriteDependencies(writer);

            // Finish the document
            writer.WriteEndElement();
            writer.WriteEndDocument();

            // Finish writing
            writer.Close();

            // Calculate elapsed time
            DateTime stopTime = DateTime.Now;
            TimeSpan elapsedTime = stopTime - startTime;
        }

        // Reload the state of the engine from file
        public static void DeserializeState(string Filename)
        {
            // Get the start time
            DateTime startTime = DateTime.Now;

            // Load the Xml document from file
            XmlDocument doc = new XmlDocument();
            doc.Load(Filename);

            // Locate the Components root node
            XmlNode ComponentsNode = doc.GetElementsByTagName("Components")[0];

            // Create a serializer
            Serializer s = new Serializer();

            // Reload the Assembly dependencies
            s.PopulateAssemblies(doc.GetElementsByTagName("Dependencies")[0]);

            // Deserialize each component in the file
            foreach(XmlNode node in ComponentsNode.ChildNodes)
                s.Deserialize(node);

            // Calculate the elapsed time
            DateTime stopTime = DateTime.Now;
            TimeSpan elapsedTime = stopTime - startTime;
        }
    }
}